﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Web.Mvc;
using Mvc.XForms.Controls.Settings;
using Mvc.XForms.Utilities;

namespace Mvc.XForms.Controls {
	public class Select1<TContext, T> : CoreControlBase<TContext>, ISelect1 {
		private readonly Expression<Func<TContext, T>> bind;
		private readonly IEnumerable<T> items;
		private readonly Func<T, object> valueSelector;
		private readonly Func<T, object> labelSelector;
		private IList<SelectListItem> selectList;

		public Select1Settings Settings { get; private set; }
		public string NullText { get; set; }
		public bool SelectNullForEmptyRequest { get; set; }

		public override XFormControlType ControlType {
			get { return XFormControlType.Select1; }
		}

		public Select1(IContainerControl<TContext> parent, Expression<Func<TContext, T>> bind, IEnumerable<T> items, Expression<Func<T, object>> valueSelector, Func<T, object> labelSelector)
			: base(parent, bind.Body) {
			if (items == null)
				throw new ArgumentNullException("items", string.Format("Select1 items for binding \"{0}\" cannot be null", bind));

			Settings = new Select1Settings(this);
			CssClass.Add("select1");
			Appearance = Appearance.Minimal;

			this.bind = bind;
			this.items = items;
			this.valueSelector = valueSelector.Compile();
			this.labelSelector = labelSelector;

			var valueSelectorID = valueSelector.Body.ExtractID();
			if (valueSelectorID.Length > 0) {
				ID = string.Concat(ID, ExpressionHelper.Separator, valueSelectorID);
				FullID = string.Concat(FullID, ExpressionHelper.Separator, valueSelectorID);
			}
		}

		public override void SetValueFromRequest() {
			if (XForm.FormCollection[ID] != null) {
				var reqValue = XForm.FormCollection[ID];
				Value = reqValue;
			}
		}

		protected override Func<object> SetValueAction {
			get {
				return () => {
					// When the request is empty (ie an "Add" form) it might be desirable to
					// select the null option. Useful for enumeration situations as a model
					// enum property, for example, cannot be null.
					if (NullText != null && SelectNullForEmptyRequest)
						return string.Empty;

					if (Parent.ModelContext == null) {
						return string.Empty;
					}
					var selectedObj = bind.Compile()(Parent.ModelContext);
					if (selectedObj is T)
						return valueSelector((T)selectedObj).ToString();
					if (this.Value != null)
						return selectedObj.ToString();

					return string.Empty;
				};
			}
		}

		public override string Render() {
			Label = Label ?? bind.Body.ExtractMember().CapitalizeSpace();

			if (Appearance == Appearance.Full) {
				CssClass.Add("full");
				return XForm.Formatter.Select1Full(this);
			}

			return XForm.Formatter.Select1(this);
		}

		public IList<SelectListItem> SelectList {
			get {
				if (selectList == null) {
					selectList = new List<SelectListItem>();
					var selectedValue = this.Value.ToString();

					this.items.ForEach(t => {
						var listItem = new SelectListItem();

						var valueStr = valueSelector(t);
						listItem.Value = valueStr == null ? string.Empty : valueStr.ToString();

						var labelStr = labelSelector(t);
						listItem.Text = labelStr == null ? string.Empty : labelStr.ToString();

						listItem.Selected = listItem.Value == selectedValue;
						selectList.Add(listItem);
					});

					// Add null option if necessary
					if (NullText != null) {
						var listItem = new SelectListItem();
						listItem.Value = string.Empty;
						listItem.Text = NullText;
						selectList.Insert(0, listItem);
					}

					// Select first item in list if none selected to comply with standards
					// 
					//if (selectList.Count > 0 && !selectList.Any(li => li.Selected))
					//	selectList[0].Selected = true;
				}

				return selectList;
			}
		}

		public static IList<T> GetEnumList(Expression<Func<TContext, T>> enumBind) {
			IList<T> enums;
			if (!EnumHelper.TryGetEnumList(out enums))
				throw new ArgumentException(string.Format("Enum bind ({0}) must point to an Enum or Nullable Enum", enumBind));

			return enums;
		}
	}
}